﻿using System;
using Microsoft.Practices.Unity;
using WDA.Config;
using System.Threading;
using WDA.Core.DynamicReconfiguration;
using System.Web;
using WDA.Public;
using System.ServiceModel.Channels;

namespace WDA.Core
{
	/// <summary>
	/// WDAB facade implementation. Provides means to communicate with WDAB-based services.
	/// </summary>
   public sealed class Services : IServices
	{
		#region Fields
		private readonly IUnityContainer _outerContainer;
		private readonly string _configName;
		private readonly string _hostName;
		private readonly bool _addAsChildContainer;
		private RuntimeBase _runtime;
		private bool _disposed;
		private readonly IUnityContainer _persistentContainer;
		private readonly ReaderWriterLockSlim _configLock;
		#endregion

		/// <summary>
		/// Creates new <see cref="Services"/> instance. In case of web server context, a limited instance is created
		/// (<see cref="WebAppRuntime"/>). It only allows to consume services. In this case an auto-reconfiguration
		/// engine is not started, because typicaly <see cref="Services"/> instance lives as short as request
		/// processing time and for another request, another instance (with possibly newer configuration) is
		/// creates. Another specific feature of web context is that proxy instances returned by the instance
		/// are managed and closes internally.
		/// </summary>
		/// <param name="outerContainer">Outer (configuring) container.</param>
		/// <param name="configName">WDAB configuration name.</param>
		/// <param name="hostName">WDAB host name.</param>
		/// <param name="addAsChildContainer">Shoud create main general-purpos container as child container of outer one?</param>
		public Services(IUnityContainer outerContainer, string configName, string hostName, bool addAsChildContainer)
		{						
			_outerContainer = outerContainer;
			_configName = configName;
			_hostName = hostName;
			_addAsChildContainer = addAsChildContainer;

			_persistentContainer = new UnityContainer();
			_persistentContainer.RegisterInstance(_persistentContainer);
			_persistentContainer.RegisterInstance(WDAContainerNames.PersistentInternalContainerName, _persistentContainer);
         _persistentContainer.RegisterInstance<IServices>(this);


			IConfigurationManager configManager = _outerContainer.Resolve<IConfigurationManager>();
			IConfiguration config = configManager.GetConfiguration(_configName, false);

			RuntimeVersion v = new RuntimeVersion { VersionNumber = config.Version };
			_persistentContainer.RegisterInstance(v);

			if (HttpContext.Current == null)
			{
				_configLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
				_persistentContainer.RegisterInstance(_configLock);
				ReconfigurationProxy configProxy = new ReconfigurationProxy(Reconfigure);
				_persistentContainer.RegisterInstance(_persistentContainer.BuildUp(configProxy));				
				_runtime = new FullRuntime();
			}
			else
			{
				_runtime = new WebAppRuntime();
			}
			_runtime.Init(_outerContainer, _persistentContainer, _configName, _hostName, _addAsChildContainer);
		}

		private void Reconfigure()
		{
			Reconfigure(false);
		}

		/// <summary>
		/// Initializes host reconfiguration process. Internally checks if current configuration version return by
		/// <see cref="IConfigurationManager"/> is newer than one that is implemented by runtime component. If so,
		/// the component is released and a new one is created according to lates configuration data.		
		/// </summary>
		/// <param name="notifyRemoteHosts">Should inform remote hosts of configuration version change?</param>
		public void Reconfigure(bool notifyRemoteHosts)
		{
			try
			{
				_configLock.EnterWriteLock();
				RuntimeVersion v = _persistentContainer.Resolve<RuntimeVersion>();
				ReconfigurationProxy reconfigProxy = _persistentContainer.Resolve<ReconfigurationProxy>();				

				IConfigurationManager configManager = _outerContainer.Resolve<IConfigurationManager>();
				IConfiguration config = configManager.GetConfiguration(_configName, false);

				if (config.Version > v.VersionNumber)
				{
					if (notifyRemoteHosts)
					{
						reconfigProxy.ForceRemoteReconfiguration(config.Version);
					}

					_runtime.Dispose();
					v.VersionNumber = config.Version;
					_runtime = new FullRuntime();
					_runtime.Init(_outerContainer, _persistentContainer, _configName, _hostName, _addAsChildContainer);					
				}
			}
			finally
			{
				_configLock.ExitWriteLock();
			}
      }

      #region IServices Members      
		public object Get(Type serviceType)
		{			
			try
			{
				if (_configLock != null)
				{
					_configLock.EnterReadLock();
				}
				return _runtime.Get(serviceType);
			}
			finally
			{
				if (_configLock != null)
				{
					_configLock.ExitReadLock();
				}
			}
		}		
		public T Get<T>()
		{
			return (T)Get(typeof(T));
		}
      public void Communicate<T>(Action<T> proxyAction)
      {
         T proxy = Get<T>();
         try
         {
            proxyAction(proxy);
         }
         finally
         {
            IChannel ch = proxy as IChannel;
            if (ch != null)
            {
               ch.Close();
            }
         }
      }

      public TResult Communicate<T, TResult>(Func<T, TResult> proxyAction)
      {
         T proxy = Get<T>();
         try
         {
            return proxyAction(proxy);
         }
         finally
         {
            IChannel ch = proxy as IChannel;
            if (ch != null)
            {
               ch.Close();
            }
         }
      }
      #endregion

		#region IDisposable Members
      /// <summary>
      /// Releases runtime instance.
      /// </summary>
		public void Dispose()
		{
			if (!_disposed)
			{
				_runtime.Dispose();				
				_disposed = true;
			}
			GC.SuppressFinalize(this);
		}
		#endregion      
   }
}
